using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using igp.net.DataModel;
using Parser;

namespace igp.net.DataModel
{
    /// <summary>
    /// Represents a custom function defined by a formula.
    /// </summary>
    public class IgpFunction : IgpFunctionBase
    {
        ///<summary>
        /// Determines if this function should be treated as a series.
        /// In such case the result of value computation will be a sum
        /// of value for n = SeriesLowerLimit, ..., SeriesLimit
        ///</summary>
        public bool IsSeries { get; set; }

        ///<summary>
        /// Lowest n so that function value is a sum of values for
        /// n = SeriesLowerLimit, ..., SeriesLimit
        ///</summary>
        public int SeriesLowerLimit { get; set; }

        ///<summary>
        /// Highest n so that function value is a sum of values for
        /// n = SeriesLowerLimit, ..., SeriesLimit
        ///</summary>
        public int SeriesLimit { get; set; }

        /// <summary>
        /// Default constructor.
        /// </summary>
        public IgpFunction()
        {
            DomainLower = -5;
            DomainHigher = 5;
            Limited = false;

            ItemName = "Function ";
            TypeID = IgpItemType.Function;
            LineWidth = 1;
            LineColor = Color.Green;

            SeriesLowerLimit = 1;
            SeriesLimit = 10;
            IsSeries = false;

            Formula = new Expression("sin(x)", new[] { "x", "n" });
        }

        /// <summary>
        /// Gets/Sets the formula.
        /// </summary>
        public Expression Formula { get; set; }

        /// <summary>
        /// Calculates values of the function for specified argument.
        /// </summary>
        /// <param name="x">argument value</param>
        /// <returns>f(x)</returns>
        public override double Value(double x)
        {
            try
            {
                return ParseFormula(x);
            }
            catch (Exception ex)
            {
                if (ex.Message.Contains("0/0"))
                {
                    return 0.5 * (ParseFormula(x + 1e-10) + ParseFormula(x - 1e-10));
                }

                throw;
            }
        }

        private double ParseFormula(double x)
        {
            Formula.SetConstant("x", x);
            
            if (!IsSeries)
            {
                try
                {
                    return Formula.Parse();
                }
                catch (Exception)
                {
                    return Double.PositiveInfinity;
                }
            }

            double result = 0;
            
            for (int n = SeriesLowerLimit; n <= SeriesLimit; n++)
            {
                Formula.SetConstant("n", n);
                result += Formula.Parse();
            }

            return result;
        }

        /// <summary>
        /// Calculates the value of the functions' derivative for the desired argument.
        /// </summary>
        /// <param name="x">argument</param>
        /// <returns>f'(x)</returns>
        public override double Derivative(double x)
        {
            const double h = 1e-10;

            return (ParseFormula(x + h) - ParseFormula(x - h)) / (2 * h);
        }

        /// <summary>
        /// Calculates the value of the functions' indefinite integral for the desired argument.
        /// The remaining constant is assumed to be 0.
        /// </summary>
        /// <param name="x">argument</param>
        /// <returns>indefinite integral(x)</returns>
        public override double Integral(double x)
        {
            return 0;
        }

        /// <summary>
        /// Saves data to a TextWriter.
        /// </summary>
        /// <param name="sw">data output</param>
        public override void SaveData(TextWriter sw)
        {
            sw.WriteLine("<function>");
            sw.WriteLine("ItemName = {0}", ItemName);
            sw.WriteLine("LineColor = {0}", LineColor.ToArgb());
            sw.WriteLine("LineWidth = {0}", LineWidth);
            sw.WriteLine("LineType = {0}", (int)LineType);

            sw.WriteLine("DomainMinimum = {0}", DomainLower);
            sw.WriteLine("DomainMaximum = {0}", DomainHigher);

            sw.WriteLine("Formula = {0}", Formula.GetExpression());
            sw.WriteLine("IsVisible = {0}", IsVisible);
            sw.WriteLine("OnLegend = {0}", VisibleOnLegend);
            
            sw.WriteLine("IsSeries = {0}", IsSeries);
            sw.WriteLine("SeriesLowerLimit = {0}", SeriesLowerLimit);
            sw.WriteLine("SeriesLimit = {0}", SeriesLimit);

            sw.WriteLine("</function>");
        }

        /// <summary>
        /// Loads data from a TextReader.
        /// </summary>
        /// <param name="sr">data input</param>
        public override void ReadData(TextReader sr)
        {
            String Line;
            while ((Line = sr.ReadLine()) != "</function>")
            {
                char[] sep = { '=', '|' };
                String[] cmd = Line.Split(sep, 6);

                if (cmd[0].StartsWith("ItemName"))
                {
                    ItemName = cmd[1].Substring(1, cmd[1].Length - 1);
                    continue;
                }

                if (cmd[0].StartsWith("LineColor"))
                {
                    LineColor = Color.FromArgb(int.Parse(cmd[1]));
                    continue;
                }

                if (cmd[0].StartsWith("LineWidth"))
                {
                    LineWidth = int.Parse(cmd[1]);
                    continue;
                }

                if (cmd[0].StartsWith("LineType"))
                {
                    LineType = (DashStyle)int.Parse(cmd[1]);
                    continue;
                }

                if (cmd[0].StartsWith("LineWidth"))
                {
                    LineWidth = int.Parse(cmd[1]);
                    continue;
                }

                if (cmd[0].StartsWith("DomainMinimum"))
                {
                    DomainLower = double.Parse(cmd[1]);
                    continue;
                }

                if (cmd[0].StartsWith("DomainMaximum"))
                {
                    DomainHigher = double.Parse(cmd[1]);
                    continue;
                }

                if (cmd[0].StartsWith("Formula"))
                {
                    Formula.SetExpression(cmd[1].Substring(1, cmd[1].Length - 1));
                    continue;
                }

                if (cmd[0].StartsWith("Visible"))
                {
                    IsVisible = bool.Parse(cmd[1]);
                    continue;
                }

                if (cmd[0].StartsWith("OnLegend"))
                {
                    VisibleOnLegend = bool.Parse(cmd[1]);
                    continue;
                }
                
                if (cmd[0].StartsWith("IsSeries"))
                {
                    IsSeries = bool.Parse(cmd[1]);
                    continue;
                }

                if (cmd[0].StartsWith("SeriesLowerLimit"))
                {
                    SeriesLowerLimit = int.Parse(cmd[1]);
                    continue;
                }

                if (cmd[0].StartsWith("SeriesLimit"))
                {
                    SeriesLimit = int.Parse(cmd[1]);
                    continue;
                }
            }
        }
    }
}